# coding: utf-8

# -------------------------------------------------------------------------------
# Name:         api.py
# Description:  
# Author:       XiangjunZhao
# EMAIL:        2419352654@qq.com
# Date:         2019/11/18 17:32
# -------------------------------------------------------------------------------
import json
import os
import time
from copy import deepcopy

from django.db import transaction
from django.http import StreamingHttpResponse
from rest_framework import status
from rest_framework.generics import DestroyAPIView
from rest_framework.mixins import UpdateModelMixin, CreateModelMixin
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet

from apps.BasicAuthService.models import Role
from apps.HttpAutoTestService.core import utils
from apps.HttpAutoTestService.core.builtin.functions import get_timestamp
from apps.HttpAutoTestService.core.ext_method_runner import ExtMethodRunner
from apps.HttpAutoTestService.core.http_client import HttpSession
from apps.HttpAutoTestService.core.http_session_context import HttpSessionContext
from apps.HttpAutoTestService.core.http_testcase_debuger import HttpTestcaseDebuger
from apps.HttpAutoTestService.filters import ApiFilter, ApiNameFilter
from apps.HttpAutoTestService.models import Api, Testcase, Project, TestcaseFolder, Module, Testsuite, \
    Testsuite2Testcase
from apps.HttpAutoTestService.serializers import ApiListSerializer, ApiSerializer, ApiNameListSerializer
from apps.HttpAutoTestService.utils import file_reader
from apps.HttpAutoTestService.utils.import_api_task import *

__all__ = ['ApiNameListViewSet', 'ApiListViewSet', 'ApiViewSet', 'ApiBatchDestroyAPIView', 'ApiImportAPIView',
           'ApiMoveAPIView', 'ApiTemplateDownloadAPIView', 'ApiUploadAPIView', 'ApiDebugAPIView',
           'ApiBatchCreateAPIView']

from utils.excel_util import ExcelReadUtil


class ApiNameListViewSet(ReadOnlyModelViewSet):
    """
    api名称列表
    """
    serializer_class = ApiNameListSerializer
    filter_class = ApiNameFilter
    pagination_class = None

    def get_queryset(self):
        user = self.request.user
        apis = Api.objects.filter(is_deleted=False).values('id', 'name', 'url', 'method')
        if not Role.objects.filter(code='SUPERADMIN', user=user, is_deleted=False):
            apis = apis.filter(project__members=user).distinct()
        return apis


class ApiListViewSet(ReadOnlyModelViewSet):
    """
    api列表
    """
    serializer_class = ApiListSerializer
    filter_class = ApiFilter

    def get_queryset(self):
        user = self.request.user
        apis = Api.objects.filter(is_deleted=False)
        if not Role.objects.filter(code='SUPERADMIN', user=user, is_deleted=False):
            apis = apis.filter(project__members=user).distinct()
        return apis


class ApiViewSet(CreateModelMixin, UpdateModelMixin, GenericViewSet):
    """
    api创建（用例创建）、更新
    """
    queryset = Api.objects.filter(is_deleted=False)
    serializer_class = ApiSerializer

    def create(self, request, *args, **kwargs):
        user = request.user
        data = request.data
        api_data = deepcopy(data)
        if hasattr(api_data, 'create_api_and_testcase'):
            del api_data['create_api_and_testcase']
        del api_data['expect_result']
        serializer = self.get_serializer(data=api_data)
        serializer.is_valid(raise_exception=True)
        api = serializer.save()
        api.creator = api.modifier = user
        api.save()

        create_api_and_testcase = data.get('create_api_and_testcase', False)
        if create_api_and_testcase:
            # 同时创建测试用例
            project = data.get('project')
            testcase_folder = TestcaseFolder.objects.filter(project_id=project, is_system=True,
                                                            is_deleted=False).first()
            name = data.get('name')
            if Testcase.objects.filter(name=name):
                name = "{name}-{timestamp}".format(name=name, timestamp=str(get_timestamp(16))[10:])

            testcase_data = {
                'name': name,
                'url': data.get('url'),
                'headers': data.get('headers'),
                'request_data_type': data.get('request_data_type'),
                'request_params': data.get('request_params'),
                'request_data': data.get('request_data'),
                'expect_result': data.get('expect_result'),
                'api_id': api.id,
                'testcase_folder_id': testcase_folder.id,
            }
            testcase = Testcase(**testcase_data)
            testcase.creator = testcase.modifier = user
            testcase.save()

        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)


class ApiBatchDestroyAPIView(DestroyAPIView):
    """
    api批量删除
    """

    def delete(self, request, *args, **kwargs):
        cascade = request.data.get('cascade')  # 级联 true：级联删除用例，false：只删除不含有用例的接口
        api_ids = request.data.get('apis')
        if cascade:
            Api.objects.filter(id__in=api_ids).update(is_deleted=True)
            Testcase.objects.filter(api_id__in=api_ids).update(is_deleted=True)
        else:
            Api.objects.filter(id__in=api_ids).exclude(
                id__in=Testcase.objects.filter(api_id__in=api_ids, is_deleted=False).values_list(
                    'api_id', flat=True).distinct()).update(is_deleted=True)
        return Response(status=status.HTTP_204_NO_CONTENT)


class ApiImportAPIView(APIView):
    """
    从postman、har、yaml、yapi文件导入接口
    """
    import_api_fun = {
        'postman': import_api_from_postman,
        'har': import_api_from_har,
        'yaml': import_api_from_yaml,
        'yapi': import_api_from_yapi
    }

    def post(self, request, *args, **kwargs):
        user = request.user
        # 所属项目
        project = Project.objects.filter(id=request.data.get('project'), is_deleted=False).first()
        # 文件来源：postman、swagger
        source = request.data.get('source')
        # 文件列表
        files = request.data.getlist('files')
        file_contents = [bytes.decode(item.read(), encoding='utf-8') for item in files]
        self.import_api_fun[source](user=user, project=project, contents=file_contents)
        return Response(status=status.HTTP_200_OK, data='上传成功', content_type='application/json')


class ApiMoveAPIView(APIView):
    """
    接口搬家
    """

    def post(self, request, *args, **kwargs):
        data = request.data
        module = data.get('module')
        apis = data.get('apis')
        if module:
            Api.objects.filter(id__in=apis).update(module=module)
        return Response(status=status.HTTP_200_OK, data='接口搬家成功……', content_type='application/json')


class ApiTemplateDownloadAPIView(APIView):
    """
    下载批量创建用例模板
    """

    def get(self, request, *args, **kwargs):
        base_dir = os.path.dirname(os.path.dirname(__file__))
        testcase_template = os.path.join(os.path.join(base_dir, 'templates'), '批量创建接口模板.xlsx')
        response = StreamingHttpResponse(file_reader.read_file(testcase_template))
        response['Content-Type'] = 'application/octet-stream'
        response['Content-Disposition'] = 'attachment;filename="批量创建接口模板.xlsx"'
        return response


class ApiUploadAPIView(APIView):
    """
    上传excel文件，并读取内容
    """

    def post(self, request, *args, **kwargs):
        # 文件列表
        file = request.FILES.get('file')
        title = ['name', 'type', 'url_or_ext_method', 'method', 'headers', 'request_data_type', 'request_data',
                 'create_testcase', 'expect_result', 'remark']
        excel_data = ExcelReadUtil(file_contents=file.read()).get_datas(start_row=1, end_col=10)
        excel_data = [dict(zip(title, item)) for item in excel_data]
        default_headers = json.dumps({"Content-Type": "application/json"}, ensure_ascii=False)
        default_request_data = json.dumps({}, ensure_ascii=False)
        default_http_api_expect_result = json.dumps(
            {"output": [], "validate": [{"check": "status_code", "expect": 200, "comparator": "eq"}]},
            ensure_ascii=False)
        default_ext_method_expect_result = json.dumps({}, ensure_ascii=False)
        results = []
        for item in excel_data:
            if item.get('create_testcase') == '':
                item['create_testcase'] = 'YES'
            if item.get('name') != '' and item.get('url_or_ext_method') != '':
                if item.get('type') == 'HTTP_API':
                    item['url_or_ext_method'] = item.get('url_or_ext_method').strip().lstrip('/').strip()
                    if item.get('method') == '':
                        item['method'] = 'POST'
                    if item.get('headers') == '':
                        item['headers'] = default_headers
                    if item.get('request_data_type') == '':
                        item['request_data_type'] = 'Json'
                    if item.get('request_data') == '':
                        item['request_data'] = default_request_data
                    if item.get('expect_result') == '':
                        item['expect_result'] = default_http_api_expect_result
                elif item.get('type') == 'EXT_METHOD':
                    if item.get('expect_result') == '':
                        item['expect_result'] = default_ext_method_expect_result
                results.append(item)
        return Response(status=status.HTTP_200_OK, data=results, content_type='application/json')


class ApiDebugAPIView(APIView):
    """
    从EXCEL导入接口功能，接口调试/扩展方法调试
    """

    @staticmethod
    def http_testcase_debug(http_session, http_session_context, base_url, api):
        """
        http用例调试
        Args:
            http_session:
            http_session_context:
            base_url:
            api:

        Returns:

        """
        name = api.get('name')
        url = api.get('url_or_ext_method')
        url = utils.build_url(base_url=base_url, url=url)
        method = api.get('method')
        headers = api.get('headers')
        request_data_type = api.get('request_data_type')
        request_data = api.get('request_data')
        expect_result = api.get('expect_result')
        api_debug_kwargs = {'http_session': http_session, 'http_session_context': http_session_context,
                            'name': name, 'url': url, 'method': method, 'cookies': None, 'headers': headers,
                            'request_data_type': request_data_type, 'expect_result': expect_result}
        if request_data_type == 'Json':
            api_debug_kwargs.update({
                'json_data': request_data
            })
        elif request_data_type == 'Form Data':
            api_debug_kwargs.update({
                'form_data': request_data
            })
        # 创建HttpTestcaseDebuger对象实例
        http_testcase_debuger = HttpTestcaseDebuger(**api_debug_kwargs)
        testcase_result = http_testcase_debuger.debug()
        response_data = testcase_result.get('actual_response_data')
        failure_reason = testcase_result.get('failure_reason')
        output_result = testcase_result.get('output_result')
        if isinstance(response_data, (list, dict, tuple)):
            response_data = json.dumps(response_data, ensure_ascii=False)
        if isinstance(failure_reason, (list, dict, tuple)):
            failure_reason = json.dumps(failure_reason, ensure_ascii=False)
        if isinstance(output_result, (list, dict, tuple)):
            output_result = json.dumps(output_result, ensure_ascii=False)
        api.update({
            'status_code': testcase_result.get('actual_status_code'),
            'response_data': response_data,
            'status': testcase_result.get('status'),
            'failure_reason': failure_reason,
            'output_result': output_result,
        })

    @staticmethod
    def ext_method_debug(http_session_context, api):
        """
        扩展方法调试
        Args:
            http_session_context:
            api:

        Returns:

        """
        name = api.get('name')
        ext_method = api.get('url_or_ext_method')
        expect_result = api.get('expect_result')
        ext_method_debug_kwargs = {
            'http_session_context': http_session_context,
            'ext_method_name': name,
            'ext_method': ext_method,
            'expect_result': expect_result
        }
        ext_method_runner = ExtMethodRunner()
        ext_method_result = ext_method_runner.debug(**ext_method_debug_kwargs)
        response_data = ext_method_result.get('ext_method_run_result')
        failure_reason = ext_method_result.get('failure_reason')
        output_result = ext_method_result.get('output_result')
        if isinstance(response_data, (list, dict, tuple)):
            response_data = json.dumps(response_data, ensure_ascii=False)
        if isinstance(failure_reason, (list, dict, tuple)):
            failure_reason = json.dumps(failure_reason, ensure_ascii=False)
        if isinstance(output_result, (list, dict, tuple)):
            output_result = json.dumps(output_result, ensure_ascii=False)
        api.update({
            'response_data': response_data,
            'status': ext_method_result.get('status'),
            'failure_reason': failure_reason,
            'output_result': output_result,
        })

    def post(self, request, *args, **kwargs):
        # 接口所属项目id
        project = request.data.get('project')
        project = Project.objects.get(id=project)
        project_variables = project.global_variables
        default_env = project.default_environment
        env_variables = default_env.global_variables
        base_url = default_env.base_url
        # 接口数据
        api_data = request.data.get('api')
        # 创建HttpSession实例
        http_session = HttpSession()
        # 创建HttpSessionContext实例
        http_session_context = HttpSessionContext(environment_variables=env_variables,
                                                  project_variables=project_variables)
        for api in api_data:
            if api.get('create_testcase').upper() == 'YES':
                if api.get('type') == 'HTTP_API':
                    self.http_testcase_debug(http_session, http_session_context, base_url, api)
                elif api.get('type') == 'EXT_METHOD':
                    self.ext_method_debug(http_session_context, api)
        return Response(status=status.HTTP_200_OK, data=api_data, content_type='application/json')


class ApiBatchCreateAPIView(APIView):
    """
    批量创建接口、用例、场景、场景组织用例
    """

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        # 当前登录用户
        user = request.user
        # 接口所属项目id
        project = request.data.get('project')
        # 接口数据
        api_data = request.data.get('api')
        # 是否创建测试场景：True or False
        create_testsuite = request.data.get('create_testsuite')
        # 场景名称
        testsuite_name = request.data.get('testsuite_name')
        # 是否创建测试用例夹：True or False
        create_testcase_folder = request.data.get('create_testcase_folder')
        # 用例夹名称
        testcase_folder_name = request.data.get('testcase_folder_name')
        # 项目默认模块
        default_module = Module.objects.filter(project_id=project, is_system=True, is_deleted=False).first()

        result = {
            'exist_api': 0,
            'new_api': 0,
            'new_testcase': 0,
            'new_testsuite': 0,
            'new_testsuite_name': '',
            'new_testcase_folder': 0,
            'new_testcase_folder_name': '',
        }
        # 存放新增的接口
        new_api_list = []
        # 存放新增的测试用例，用于组织测试场景
        new_testcase_list = []

        if create_testcase_folder:
            # 创建用例夹
            testcase_folder = TestcaseFolder.objects.filter(project_id=project, name=testcase_folder_name,
                                                            is_deleted=False).first()
            if testcase_folder:
                # 同名用例夹已存在，在用例夹名称后增加6位时间戳
                testcase_folder_name = f'{testcase_folder_name}-{str(get_timestamp(16))[10:]}'
            testcase_folder = TestcaseFolder(project_id=project, name=testcase_folder_name, creator=user, modifier=user)
            testcase_folder.save()
            result['new_testcase_folder'] = 1
            result['new_testcase_folder_name'] = testcase_folder_name
        else:
            # 项目默认用例夹
            testcase_folder = TestcaseFolder.objects.filter(project_id=project, is_system=True,
                                                            is_deleted=False).first()
        for item in api_data:
            name = item.get('name')
            create_testcase = item.get('create_testcase')
            expect_result = json.loads(item.get('expect_result'))
            remark = item.get('remark')
            if item.get('type') == 'HTTP_API':
                url = item_url = item.get('url_or_ext_method')
                request_params = ''
                index = item_url.find('?')
                if index != -1:
                    url = item_url[:index]
                    request_params = item_url[index + 1:]
                method = item.get('method')
                headers = json.loads(item.get('headers', '{}'))
                request_data_type = item.get('request_data_type')
                request_data = json.loads(item.get('request_data', '{}'))

                # 查询系统是否已存在接口
                api = Api.objects.filter(project_id=project, url=url, method=method, is_deleted=False).first()
                if not api:
                    # 接口不存在，创建新接口
                    api = Api(name=name, url=url, method=method, headers=headers, request_params=request_params,
                              request_data_type=request_data_type, request_data=request_data, remark=remark,
                              project_id=project, module=default_module, creator=user, modifier=user)
                    api.save()
                    new_api_list.append(api)

                # 创建测试用例
                if create_testcase == "YES":
                    if Testcase.objects.filter(name=name):
                        time.sleep(0.1)
                        name = f'{name}-{str(get_timestamp(16))[10:]}'
                    testcase = Testcase(name=name, url=url, headers=headers, request_params=request_params,
                                        request_data_type=request_data_type, request_data=request_data,
                                        expect_result=expect_result, remark=remark, api=api,
                                        testcase_folder=testcase_folder, creator=user, modifier=user)
                    testcase.save()
                    new_testcase_list.append(testcase)
            elif item.get('type') == 'EXT_METHOD':
                ext_method = item.get('url_or_ext_method')
                # 创建扩展方法
                if create_testcase == 'YES':
                    new_testcase_list.append(
                        {'ext_method_name': name, 'ext_method': ext_method, 'expect_result': expect_result}
                    )

        if create_testsuite:
            # 创建场景
            testsuite = Testsuite(project_id=project, name=testsuite_name, global_variables='{}', creator=user,
                                  modifier=user)
            testsuite.save()
            result['new_testsuite'] = 1
            result['new_testsuite_name'] = testsuite_name
            # 组织场景用例
            testsuite2testcase_list = []
            for index, testcase in enumerate(new_testcase_list):
                if isinstance(testcase, Testcase):
                    testsuite2testcase = Testsuite2Testcase(testsuite=testsuite, testcase=testcase, type='HTTP_API',
                                                            sort=index, is_execute=True, testsuite_name=testsuite_name,
                                                            testcase_name=testcase.name, url=testcase.url,
                                                            headers=testcase.headers,
                                                            request_data_type=testcase.request_data_type,
                                                            request_params=testcase.request_params,
                                                            request_data=testcase.request_data,
                                                            expect_result=testcase.expect_result)
                else:
                    testsuite2testcase = Testsuite2Testcase(testsuite=testsuite, testsuite_name=testsuite_name,
                                                            type='EXT_METHOD', sort=index, is_execute=True,
                                                            ext_method_name=testcase.get('ext_method_name'),
                                                            ext_method=testcase.get('ext_method'),
                                                            expect_result=testcase.get('expect_result'))
                testsuite2testcase_list.append(testsuite2testcase)
            Testsuite2Testcase.objects.bulk_create(testsuite2testcase_list)

        result['new_api'] = len(new_api_list)
        result['exist_api'] = len(api_data) - result['new_api']
        result['new_testcase'] = len(new_testcase_list)
        return Response(status=status.HTTP_200_OK, data=result, content_type='application/json')
